package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsRecord;
import com.tianji.learning.domain.vo.PointsStatisticsVO;
import com.tianji.learning.enums.PointsRecordType;
import com.tianji.learning.mapper.PointsRecordMapper;
import com.tianji.learning.service.IPointsRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.tianji.learning.constants.LearningConstants.POINTS_RECORD_TABLE_PREFIX;

/**
 * <p>
 * 学习积分记录，每个月底清零 服务实现类
 * </p>
 *
 * @author zr
 * @since 2024-07-19
 */
@Service
@RequiredArgsConstructor
public class PointsRecordServiceImpl extends ServiceImpl<PointsRecordMapper, PointsRecord> implements IPointsRecordService {

    private final StringRedisTemplate redisTemplate;
    /**
     * 为用户添加积分记录。
     * 此方法用于记录用户积分的变化情况，通过创建并保存一个积分记录对象来实现。
     * 积分的增加或减少由传入的积分值决定，而积分记录的类型则由sign参数确定。
     *
     * @param userId 用户的ID，用于标识积分变化的用户。
     * @param points 积分值。
     * @param sign   积分记录的类型，用于区分不同的积分操作场景。
     */
    @Override
    public void addPointsRecord(Long userId, Integer points, PointsRecordType sign) {
        // 1.校验参数
        LocalDateTime now = LocalDateTime.now();
        if(userId == null || points == null){
            return;
        }
        // 实际可以增加积分
        int realPoints = points;
        // 2.判断当前方式有没有积分上限
        if(sign.getMaxPoints() > 0){
            //2.1 查询该用户当日此类型积分获取是否上限

            LocalDateTime startTime = DateUtils.getDayStartTime(now);
            LocalDateTime endTime = DateUtils.getDayEndTime(now);
            QueryWrapper<PointsRecord> wrapper = new QueryWrapper<>();
            wrapper.select("SUM(points) AS totalPoints");
            wrapper.eq("user_id", userId);
            wrapper.eq("type", sign.getValue());
            wrapper.between("create_time", startTime, endTime);
            Map<String, Object> map = getMap(wrapper);
            int currentPoints=0;//当前用户已得积分
            if(map != null){
                BigDecimal totalPoints = (BigDecimal)map.get("points");
                currentPoints = totalPoints.intValue();
            }
            if(currentPoints >= sign.getMaxPoints()) {
                // 2.2.超过，直接结束
                return;
            }
            // 2.3 如果积分超过上限，则只记录上限值，避免积分累加溢出
            if(currentPoints + points > sign.getMaxPoints()){
                realPoints = sign.getMaxPoints() - currentPoints;
            }
        }
        // 3.没有，直接保存积分记录
        PointsRecord p = new PointsRecord();
        p.setPoints(realPoints);
        p.setUserId(userId);
        p.setType(sign);
        save(p);
        // 4.更新总积分到Redis
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + now.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        redisTemplate.opsForZSet().incrementScore(key, userId.toString(), realPoints);
    }

    /**
     * 查询今天的积分统计信息。
     * <p>
     * 本方法用于获取当前用户今天的所有积分统计信息。返回一个包含积分统计详情的列表，
     * 每个统计详情都包含了积分的获取和情况，以及其他相关的统计信息。
     *
     * @return List<PointsStatisticsVO> 返回一个积分统计信息的列表。
     */
    @Override
    public List<PointsStatisticsVO> queryMyPointsToday() {
        //1.获取用户
        Long user = UserContext.getUser();
        //2.获取当天开始时间和结束时间
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startTime = DateUtils.getDayStartTime(now);
        LocalDateTime endTime = DateUtils.getDayEndTime(now);

        //3.查询积分记录
        QueryWrapper<PointsRecord> wrapper = new QueryWrapper<>();
        wrapper.select("type, SUM(points) AS points");
        wrapper.eq("user_id", user);
        wrapper.between("create_time", startTime, endTime);
        wrapper.groupBy("user_id", "type");
        List<PointsRecord> records = list(wrapper);

        Map<PointsRecordType, PointsRecord> map=null;
        if(CollUtils.isNotEmpty(records)){
            map = records.stream().collect(Collectors.toMap(PointsRecord::getType, c -> c));
        }
        ArrayList<PointsStatisticsVO> list = new ArrayList<>();

        //4.遍历几种枚举值，组装VO
        for (PointsRecordType type : PointsRecordType.values()) {
            PointsStatisticsVO vo = new PointsStatisticsVO();
            //5.如果该类型积分有记录，则取出积分值，否则默认0
            if(map!= null && map.containsKey(type)){
                vo.setPoints(map.get(type).getPoints());
            }else{
                vo.setPoints(0);
            }
            vo.setType(type.getDesc());
            vo.setMaxPoints(type.getMaxPoints());
            list.add(vo);
        }
        return list;
    }

    /**
     * 根据赛季创建点记录表
     * <p>
     * 本方法用于在数据库中根据指定的赛季创建一个新的积分记录表。这需要在数据库管理
     * 系统中执行相应的SQL语句来完成表的创建。表的命名和结构将根据赛季参数进行
     * 生成，以便于区分不同赛季的点记录。
     *
     * @param season 赛季编号
     *               使用整数类型表示赛季，该参数将被用于构造点记录表的名称，以区分
     *               不同赛季的表。赛季编号应为非负整数，且在数据库中应为唯一值。
     */
    @Override
    public void createPointsRecordTableBySeason(Integer season) {
        getBaseMapper().createPointsRecordTable(POINTS_RECORD_TABLE_PREFIX + season);
    }

}
